PostgreSQL: table referencing only part of a composite primary key - sql

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
);

Related

SQL: Is creating multiple foreign keys with one statement equivalent to creating them with one statement?

For example let's have the following table definition:
CREATE TABLE table1
(
id INT UNIQUE,
name VARCHAR(100) UNIQUE,
description VARCHAR(100),
PRIMARY KEY (id, name)
);
Now I would like to create another table which would have a foreign key to the above composite primary key. Would the following two statements be equivalent?
1)
CREATE TABLE table2
(
id INT PRIMARY KEY,
table1_id INT,
table1_name VARCHAR(100),
FOREIGN KEY (table1_id) REFERENCES table1(id),
FOREIGN KEY (table1_name) REFERENCES table1(name)
);
2)
CREATE TABLE table2
(
id INT PRIMARY KEY,
table1_id INT,
table1_name VARCHAR(100),
FOREIGN KEY (table1_id, table1_name) REFERENCES table1(id, name),
);
I noticed that behind the scenes Postgre SQL creates two FK db objects in the case of 1) and one FK object in the case of 2). Would everything work the same anyway?
Not at all. A foreign key reference should be to the primary key. In this case you have a composite primary key (although that is probably not needed, see below). Although foreign key references to unique keys are allowed (and some databases even allow foreign key references to any indexed columns), that is not a best practice.
When you use a composite primary key (your second example) you are guaranteeing that id/name are aligned in the first table. When you use separate references (your first example), you do not know that they are aligned, so the id could refer to one row in table1 and the name to another row. I doubt that is your intention.
In any case, repeating redundant data among tables is a bad practice. The better data model is:
CREATE TABLE table1 (
id INT PRIMARY KEY,
name VARCHAR(100) UNIQUE,
description VARCHAR(100),
);
CREATE TABLE table2 (
id INT PRIMARY KEY,
table1_id INT,
FOREIGN KEY (table1_id) REFERENCES table1(id)
);
Then, if you want the corresponding name, look up the name in the first table.
As a note, in Postgres, I would expect the INT to really be SERIAL so the database assigns a unique, increasing value when you insert new rows.
If you actually want two references to table1 then use two id references:
CREATE TABLE table2 (
id INT PRIMARY KEY,
table1_id INT,
table1_id_2 INT,
FOREIGN KEY (table1_id) REFERENCES table1(id),
FOREIGN KEY (table1_id_2) REFERENCES table1(id)
);

Is it possible to reference a foreign key to parent table?

CREATE TABLE Product
(
"Product_id" int,
"Stock_quantity" int,
"Product_name" varchar(50),
"Model" varchar(50),
"Average_rating" float(3),
"RAM" int,
"Color" varchar(20),
"Price" float(10),
PRIMARY KEY ("Product_id")
);
CREATE TABLE Sale
(
"Sale_id" int,
"Sale_date" date,
"Employee_id" int,
"Customer_id" int,
"Product_id" int,
"Product_quantity" int,
"Rating" int,
PRIMARY KEY ("Sale_id"),
FOREIGN KEY("Employee_id") REFERENCES Employee("Employee_id") ,
FOREIGN KEY("Customer_id") REFERENCES Customer("Customer_id") ,
FOREIGN KEY("Product_id") REFERENCES Product("Product_id")
);
CREATE TABLE Computer
(
"Type" varchar(10),
"Processor" varchar(20),
"Monitor_size" int
) inherits(Product);
CREATE TABLE Mobile
(
"Os" varchar(30),
"Screen_size" int
) inherits(Product);
Here is my tables while insertion of sale rows I get this error.
ERROR: insert or update on table "sale" violates foreign key constraint "PK_Product_id"
SQL state: 23503
Detail: Key (Product_id)=(12) is not present in table "product".
It says there is no presence of the row, but I can see them when I view the table:
I inserted every product as computer or mobile not as product.
You are stumbling over one of the many quirks with inheritance: There are no “global” constraints on an inheritance hierarchy, consequently you cannot have a foreign key to a inheritance hierarchy.
The primary key you defined on product does not extend to computer.
Even though a SELECT on product will append the results for the inheritance children as well, these are not part of the table parent and consequently cannot be used as target for the foreign key. The foreign key is between sale and product only, the inheritance children are excluded.
There is no proper way to do this with inheritance.
It is better for computer not to be an inheritance child of product, but to have a foreign key to product (with a unique constraint on it), so that the data for a computer object will be split between the two tables. This is somewhat inconvenient for queries, but you can have a foreign key that way.
Here is an example:
CREATE TABLE product (
product_id integer PRIMARY KEY,
prodname text NOT NULL
);
CREATE TABLE computer (
product_id integer PRIMARY KEY,
processor text NOT NULL,
FOREIGN KEY (product_id) REFERENCES product(product_id)
);
CREATE TABLE sale (
sale_id integer PRIMARY KEY,
product_id integer NOT NULL REFERENCES product(product_id)
);
Now there is no direct foreign key reference from sale to computer, but since the product_id of computer and product is identical, you can always find the unique computer for a sale if it exists:
SELECT p.prodname, c.processor
FROM sale s
JOIN product p USING (product_id)
LEFT JOIN computer c USING (product_id)
WHERE sale_id = 42;
If you have more product “subtypes”, you can add more left joins.

Foreign key SQL syntax

Let's suppose that there are two tables table1 and table2 where the second one references the first one.
I was wondering whether the following two forms of defining their foreign key relationship results in the same table structure of the second table.
CREATE TABLE table1(a INT, b INT, PRIMARY KEY(a, b));
1)
CREATE TABLE table2(a INT, b INT,
FOREIGN KEY(a, b) REFERENCES table1(a, b)
);
2)
CREATE TABLE table2(a INT, b INT,
FOREIGN KEY(a) REFERENCES table1(a),
FOREIGN KEY(b) REFERENCES table1(b)
);
I would presume that there is no difference but couldn't find any reference to support this claim.
First, this syntax is not correct:
CREATE TABLE table1 (
a INT PRIMARY KEY,
b INT PRIMARY KEY
);
A primary key is primary because only one can be defined. Presumably, you intend a composite primary key:
CREATE TABLE table1 (
a INT,
b INT,
PRIMARY KEY (a, b)
);
The foreign key relationships are entirely different. The second creates two foreign key relationships. With a composite primary key, it would fail, because the reference should be to a primary key (or at least a unique key).

Deleting related rows from other tables without primary key

I need to delete the associated rows from the "assign" table when a crew member is deleted.
However, when I create the tables, I get the error:
ERROR: there is no unique constraint matching given keys for referenced table "assign"
It seems as though I need distinct values in the "assign" table, but the whole point is that I want to delete all associated information when a crew member is removed. How can I do this?
create table flight(
flight_num BIGSERIAL PRIMARY KEY,
source_city varchar,
dest_city varchar,
dep_time int,
arr_time int,
airfare int,
mileage int
);
create table crew (
id BIGSERIAL PRIMARY KEY,
name varchar,
salary int,
position varchar,
seniority int,
fly_hours int,
mgrid int,
FOREIGN KEY (id) REFERENCES assign(id) ON DELETE CASCADE
);
create table assign (
id int, # refers to a crew member id, not a row id
flight_num int
);
The foreign keys should be on the M-M join table, as so:
create table flight(
flight_num int PRIMARY KEY,
-- etc
);
create table crew (
crew_id int PRIMARY KEY,
-- etc
);
create table assign (
crew_id int,
flight_num int,
-- If the corresponding record in either of these FKs is deleted then
-- this record will be deleted.
FOREIGN KEY (crew_id) REFERENCES crew(crew_id) ON DELETE CASCADE,
FOREIGN KEY (flight_num) REFERENCES flight(flight_num) ON DELETE CASCADE,
-- M-M usually should have a PK over both foreign columns, arranged in order
-- of expected usage. A reverse covering index can be added if needed.
PRIMARY KEY (flight_num, crew_id)
);
In this case the FKs are allowed because they are over a unique columns (PKs of crew and flight). The original error is because there was no unique contraint on the FK target (assign.crew_id).

Referencing a two column primary key with multiple foreign keys

Take the following two tables in Oracle:
Create Table A
( A int, B int, C int,
Constraint pk_ab Primary Key(A, B),
Unique (C)
);
Create Table B
( D int, E int, F int,
Constraint fk_d Foreign Key (D) References A(A),
Constraint fk_e Foreign Key (E) References A(B)
);
Why doesn't this statement work? Or more specifically, why shouldn't it work? The reason I'm trying to create this type of relation is say, in the future, I want to delete B.D, but keep the relation FK_E.
I'm getting the error:
ORA-02270: no matching unique or primary key for this column-list
"Why doesn't this statement work? Or more specifically, why shouldn't
it work? "
You have defined the primary key on A as a compound of two columns (A,B). Any foreign key which references PK_AB must match those columns in number. This is because a foreign key must identify a single row in the referenced table which owns any given row in the child table. The compound primary key means column A.A can contain duplicate values and so can column A.B; only the permutations of (A,B) are unique. Consequently the referencing foreign key needs two columns.
Create Table B
( D int, E int, F int,
Constraint fk_de Foreign Key (D,E) References A(A,B)
);
"Since there are multiple PK's that table B references"
Wrong. B references a single primary key, which happens to comprise more than one column,
" say, in the future, I want to delete B.D, but keep the relation
fk_e. "
That doesn't make sense. Think of it this way: D is not a property of B, it is an attribute B inherits through its dependence on table A.
One way to avoid this situation is to use a surrogate (or synthetic) key. Compound keys are often business keys, hence their columns are meaningful in a business context. One feature of meaningful column values is that they can change, and cascading such changes to foreign keys can be messy.
Implementing a surrogate key would look like this:
Create Table A
( id int not null, A int, B int, C int,
Constraint pk_a Primary Key(ID),
constraint uk_ab Unique (A,B)
);
Create Table B
( a_id int, F int,
Constraint fk_n_a Foreign Key (A_ID) References A(ID)
);
Of course, you could kind of do this using the schema you posted, as you already have a single column constraint on A(C). However, I think it is bad practice to reference unique constraints rather than primary keys, even though it's allowed. I think this partly because unique constraints often enforce a business key, hence meaning, hence the potential for change, but mainly because referencing primary keys just is the industry standard.
Try create two separate indexes for column's A and B before creating table B
CREATE INDEX a_idx ON A (A);
CREATE INDEX b_idx ON A (B);
But probably you need a compound FK on table B
Create Table B
( D int, E int, F int,
Constraint fk_d Foreign Key (D,E) References A(A,B)
);
A foreign key always references a PK of another table. Neither A nor B
alone are PK's.. You have multiple PK's.
Constraint fk_d Foreign Key (D,E) References A(A,B)
Also the database cannot validate a partial null multiple foreign key, so I think the check constraint also needs to be added to the table.
alter table B add constraint check_nullness
check ( ( D is not null and E is not null ) or
( D is null and E is null ) )