Foreign keys referring other foreign keys in PostgreSQL - sql

In PostgreSQL I have a database, which I intend to make the following table declaration:
CREATE TABLE canvas_user (
id INTEGER,
login_id VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(355) UNIQUE NOT NULL,
name_given VARCHAR(30),
name_family VARCHAR(30),
name_full VARCHAR(50),
role canvas_role,
last_login TIMESTAMP,
PRIMARY KEY (id)
);
CREATE TABLE problem (
id SERIAL,
title VARCHAR(50),
author VARCHAR(50),
path TEXT,
compiler VARCHAR(20),
PRIMARY KEY (id)
);
CREATE TABLE assignment (
id INTEGER,
title TEXT NOT NULL,
points_possible INTEGER NOT NULL,
problem_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY (problem_id) REFERENCES problem(id)
);
CREATE TABLE submission (
num SERIAL,
user_id INTEGER,
assignment_id INTEGER,
timestamp TIMESTAMP,
path TEXT,
lti_info TEXT[],
PRIMARY KEY(num, user_id, assignment_id),
FOREIGN KEY (user_id) REFERENCES canvas_user(id),
FOREIGN KEY (assignment_id) REFERENCES assignment(id)
);
CREATE TABLE correction (
num INTEGER,
user_id INTEGER,
assignment_id INTEGER,
timestamp TIMESTAMP,
path TEXT,
execution_time interval,
PRIMARY KEY(num, user_id, assignment_id),
FOREIGN KEY (num) REFERENCES submission(num),
FOREIGN KEY (user_id) REFERENCES submission(user_id),
FOREIGN KEY (assignment_id) REFERENCES submission(assignment_id)
);
Everything works fine, except for the following error at the creation of the last table (correction):
ERROR: there is no unique constraint matching given keys for
referenced table "submission"
What I intend with the correction table is to have an unique correction for each submission but a submission can have (or not) a correction.
How can I solve this error? Is it a problem of design or just a table declaration mistake?

A foreign key constraint does not care whether the referenced column(s) is referencing another column itself. But the referenced column(s) must be unique. That's what the error message tells you (quite clearly).
What you are missing is that a foreign key constraint can be based on multiple columns. This should work:
FOREIGN KEY (num, user_id, assignment_id) REFERENCES submission
Replacing:
FOREIGN KEY (num) REFERENCES submission(num),
FOREIGN KEY (user_id) REFERENCES submission(user_id),
FOREIGN KEY (assignment_id) REFERENCES submission(assignment_id)
The short form of the syntax (REFERENCES submission) is possible, because you are referencing the primary key, which is the default.
Plus, you can simplify: make submission.num the sinlge-column primary key, drop the redundant columns user_id and assignment_id from correction and reduce the fk constraint to just (num) - as discussed in #Tim's answer.
As long as you have the multicolumn fk constraint, consider NOT NULL constraints on each of the referencing columns (as commented by #joop). Else, one or more NULL values in the referencing columns allow to escape the fk constraint with the default MATCH SIMPLE behaviour. This may or may not be intended, typically it is not.
Alternatively consider MATCH FULL for multicolumn fk constraints to only allow that if all referencing columns are NULL. Details:
MATCH FULL vs MATCH SIMPLE

Make the foreign key from the correction table to the submission table a compound key that is unique.
Also, review the design of the submission table; num is a serial type and should be the unique primary key. You can add a unique constraint to the columns num, user_id, and assignment_id
CREATE TABLE canvas_user (
id INTEGER,
login_id VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(355) UNIQUE NOT NULL,
name_given VARCHAR(30),
name_family VARCHAR(30),
name_full VARCHAR(50),
role canvas_role,
last_login TIMESTAMP,
PRIMARY KEY (id)
);
CREATE TABLE problem (
id SERIAL,
title VARCHAR(50),
author VARCHAR(50),
path TEXT,
compiler VARCHAR(20),
PRIMARY KEY (id)
);
CREATE TABLE assignment (
id INTEGER,
title TEXT NOT NULL,
points_possible INTEGER NOT NULL,
problem_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY (problem_id) REFERENCES problem(id)
);
CREATE TABLE submission (
num SERIAL,
user_id INTEGER,
assignment_id INTEGER,
timestamp TIMESTAMP,
path TEXT,
lti_info TEXT[],
PRIMARY KEY(num, user_id, assignment_id),
FOREIGN KEY (user_id) REFERENCES canvas_user(id),
FOREIGN KEY (assignment_id) REFERENCES assignment(id)
);
CREATE TABLE correction (
num INTEGER,
user_id INTEGER,
assignment_id INTEGER,
timestamp TIMESTAMP,
path TEXT,
execution_time interval,
PRIMARY KEY(num, user_id, assignment_id),
FOREIGN KEY (num, user_id,assignment_id ) REFERENCES submission(num, user_id, assignment_id)
);

Related

SQL Constraint names

If we have query for creating table like this..
create table if not exists food
(
id int not null auto_increment,
user_id int,
name varchar(30),
constraint pk_food primary key(id,name),
foreign key(user_id) references userss(id)
);
What does pk_food mean in this example? I know this is a constraint name, but for what we should be give a name for constraint, if its working without?
create table if not exists food
(
id int not null auto_increment,
user_id int,
name varchar(30),
primary key (id, name),
foreign key (user_id) references userss(id)
);
I mean.. how to use these names and for what we need it?
You gives constraints names for basically two reasons:
You can better understand the error message when the constraint is violated.
You can more easily find the constraint if you want to delete it.

Oracle - No matching unique or primary key for this column-list

Ok, sorry if my code is not in the best format. I am getting an error when trying to create the second table. I get this error: ORA-02270: no matching unique or primary key for this column-list. I have read other posts and I am confused to what is wrong with my foreign keys. The foreign keys in E_T are D_ID, and SP_ID. D_id is referencing table d_t and SP_ID is referencing table e_t since it's a it is a unary relationship with E_ID. I believe it is employee ID and supervisor ID. The FK and PK datatypes match.
CREATE TABLE D_T
(
D_ID INTEGER NOT NULL,
D_Name VARCHAR(45),
CONSTRAINT D_T_PK PRIMARY KEY (D_ID)
);
CREATE TABLE E_T
(
E_ID INTEGER NOT NULL,
E_Name VARCHAR(45),
D_ID INTEGER,
Salary INTEGER,
SP_ID INTEGER,
CONSTRAINT E_T_PK PRIMARY KEY (E_ID),
CONSTRAINT E_T_FK1 FOREIGN KEY (D_ID) REFERENCES D_T(D_ID),
CONSTRAINT E_T_FK2 FOREIGN KEY (SP_ID) REFERENCES E_T(SP_ID)
);
I would like to note, on the Oracle server -- line 13 and 14 of my script the word "KEY" is not in blue. I am unsure if that is significant.
You are referencing the wrong column for the self-reference. So the second table should look more like:
CREATE TABLE E_T (
E_ID INTEGER NOT NULL PRIMARY KEY,
E_Name VARCHAR(45),
D_ID INTEGER,
Salary INTEGER,
SP_ID INTEGER,
CONSTRAINT E_T_FK1 FOREIGN KEY (D_ID) REFERENCES D_T(D_ID),
CONSTRAINT E_T_FK2 FOREIGN KEY (SP_ID) REFERENCES E_T(E_ID)
-------------------------------------------------------^
)

How to give name to an index, associated with constraint in H2?

I am creating table with the following code:
CREATE TABLE ELEM
(
ID INTEGER AUTO_INCREMENT NOT NULL,
TYP_ID INTEGER,
SPELL VARCHAR(50),
CONSTRAINT ELEM_PK PRIMARY KEY (ID),
CONSTRAINT ELEM_SK UNIQUE (ID, TYP_ID),
CONSTRAINT ELEM_TYP_FK FOREIGN KEY (TYP_ID) REFERENCES TYP (ID)
);
in result I get indices with the names of PRIMARY_KEY_2, ELEM_TYP_FK_INDEX_2 and CONSTRAINT_INDEX_2. Can I control these names in CREATE TABLE statement?

Cannot create table. SQL Error 02270

This is the table I am trying to create. However, I get the error
SQL Error: ORA-02270: no matching unique or primary key for this column-list
SQL:
create table Meets_In
(
cid char(20),
rno integer,
time char(20),
CONSTRAINT PRIM_KEY PRIMARY KEY(time),
constraint meets_fk1 foreign key(cid) references COURSES(CID),
constraint meets_fk2 foreign key(rno) references ROOMS(RNO)
);
These are the parent tables:
create table Courses
(
cid char(20),
cname char(20),
credits integer,
constraint CoursesKey Primary Key (cid, cname)
);
CREATE TABLE ROOMS
(
rno INTEGER,
address CHAR(20),
capacity INTEGER,
CONSTRAINT room_key PRIMARY KEY(rno)
);
I don't understand why I am getting this error.
Cause
The ORA-2270, as the error message suggests, happens when there is no matching unique or primary key for this column-list. This could be because
the parent lacks a constraint altogether
the parent table's constraint is a compound key and we haven't referenced all the columns in the foreign key statement.
Now in your COURSES table, CID is not a primary key. It is a combination of cid,cname. So for every cid, there can be multiple rows.
Now when you reference cid as foreign key for meets_in, it will not work as it violates the second point as I mentioned above.
Workaround
Add column cname in your meets_in table as well. Then use it like below.
create table Meets_In
(
cid char(20) not null,
cname char(20),
rno integer not null,
time1 char(20) not null,
CONSTRAINT PRIM_KEY PRIMARY KEY(time1),
constraint meets_fk1 foreign key(cid,cname) references COURSES (cid,cname), /*Added cid,cname */
constraint meets_fk2 foreign key(rno) references ROOMS (RNO)
);
Meets_In is acting as an associative table. Therefore its primary key should include the foreign keys into the tables it is associating.
Try a primary key consisting of: cid, cname, rno and time.
As others have noted, your primary key for courses is (cid, cname), so you also need to include both of these in your foreign key constraint meets_fk1. Or, if possible, ensure that cid only is the primary key on courses.
(I think time may be a reserved word, so perhaps consider renaming it.)

How to model m:n with 1 optional default value?

Below are two schema which I believe achieve the same result, i.e. m:n relationship with the option for a default/preferred value. Is there any reason to use one over the other?
Schema #1
CREATE TABLE people (
id serial,
primary key (id)
);
CREATE TABLE names (
id serial,
first_name text not null,
last_name text not null,
primary key (id)
);
CREATE TABLE person_has_name (
person_id integer not null references people (id),
name_id integer not null references names (id),
is_default boolean not null default false,
primary key (person_id, name_id)
);
Schema #2
CREATE TABLE people (
id serial,
default_name_id integer references names (id),
primary key (id)
);
-- this table has not changed
CREATE TABLE names (
id serial,
first_name text not null,
last_name text not null,
primary key (id)
);
CREATE TABLE person_has_name (
person_id integer not null references people (id),
name_id integer not null references names (id),
primary key (person_id, name_id)
);
Let's analyze:
Second schema prevent to you to set more than one name as default, but don't prevent to you to set as default name some not related name.
Opposite happens with first schema.
I propose to you (to your zombie user) a third schema that solves math problems:
CREATE TABLE people (
id serial,
default_name_id integer ,
primary key (id),
constraint default_person_name_fk --<--HERE
foreign key (id, default_name_id)
references person_has_name (person_id, name_id)
);
-- this table has not changed
CREATE TABLE names (
id serial,
first_name text not null,
last_name text not null,
primary key (id)
);
CREATE TABLE person_has_name (
person_id integer not null references people (id),
name_id integer not null references names (id),
primary key (person_id, name_id)
);
Regards and sorry about delay, I'm lag ;)
With the second schema you can't add people without first knowing their name. As long as this is ok (i.e. you know that whenever a person is added, they have to reveal their name), then I'd say that either of these schemas are fine, and that schema #2 might even be more optimized in the scenario where you need to query only for the default name.
I would use none of the two.
Solution 1 does not prevent you from having 2 default names for a person.
Solution 2 does not prevent a person having a default name that is not his. You could change the Foreign Key from default_name_id references names(id) to (person_id, default_name_id) references person_has_name(person_id, name_id) but then you'd have circular references and that's another problem, hard to solve.
You could use this, similar to your 2nd solution:
CREATE TABLE people (
id serial,
--- no default_name_id as foreign key here
primary key (id)
);
CREATE TABLE names (
id serial,
first_name text not null,
last_name text not null,
primary key (id)
);
CREATE TABLE person_has_name (
person_id integer not null references people (id),
name_id integer not null references names (id),
primary key (person_id, name_id)
);
With this extra table, for those persons that have a default name:
CREATE TABLE person_default_name (
person_id integer not null,
name_id integer not null,
primary key (person_id),
foreign key (person_id, name_id)
references person_has_name (person_id, name_id)
);